/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/effects/SkImageFilters.h"

#include "include/core/SkFlattenable.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSamplingOptions.h"
#include "include/private/base/SkAssert.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkPicturePriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkSamplingPriv.h"
#include "src/core/SkWriteBuffer.h"

#include <optional>
#include <utility>

namespace {
class SkImageImageFilter final : public SkImageFilter_Base {
public:
    SkImageImageFilter(sk_sp<SkImage> image, const SkRect &srcRect, const SkRect &dstRect,
        const SkSamplingOptions &sampling)
        : SkImageFilter_Base(nullptr, 0),
          fImage(std::move(image)),
          fSrcRect(srcRect),
          fDstRect(dstRect),
          fSampling(sampling)
    {
        // The dst rect should be non-empty
        SkASSERT(fImage && !dstRect.isEmpty());
    }

    SkRect computeFastBounds(const SkRect &) const override
    {
        return SkRect(fDstRect);
    }

protected:
    void flatten(SkWriteBuffer &) const override;

private:
    friend void ::SkRegisterImageImageFilterFlattenable();
    SK_FLATTENABLE_HOOKS(SkImageImageFilter)

    MatrixCapability onGetCTMCapability() const override
    {
        return MatrixCapability::kComplex;
    }

    skif::FilterResult onFilterImage(const skif::Context &) const override;

    skif::LayerSpace<SkIRect> onGetInputLayerBounds(const skif::Mapping &mapping,
        const skif::LayerSpace<SkIRect> &desiredOutput,
        std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;

    std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(const skif::Mapping &mapping,
        std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;

    sk_sp<SkImage> fImage;
    // The src rect is relative to the image's contents, so is not technically in the parameter
    // coordinate space that responds to the layer matrix (unlike fDstRect).
    SkRect fSrcRect;
    skif::ParameterSpace<SkRect> fDstRect;
    SkSamplingOptions fSampling;
};
} // end namespace

sk_sp<SkImageFilter> SkImageFilters::Image(sk_sp<SkImage> image, const SkRect &srcRect, const SkRect &dstRect,
    const SkSamplingOptions &sampling)
{
    if (srcRect.isEmpty() || dstRect.isEmpty() || !image) {
        // There is no content to draw, so the filter should produce transparent black
        return SkImageFilters::Empty();
    } else {
        SkRect imageBounds = SkRect::Make(image->dimensions());
        if (imageBounds.contains(srcRect)) {
            // No change to srcRect and dstRect needed
            return sk_sp<SkImageFilter>(new SkImageImageFilter(std::move(image), srcRect, dstRect, sampling));
        } else {
            SkMatrix srcToDst = SkMatrix::RectToRect(srcRect, dstRect);
            if (!imageBounds.intersect(srcRect)) {
                // No overlap, so draw empty
                return SkImageFilters::Empty();
            }

            // Adjust dstRect to match the updated src (which is stored in imageBounds)
            SkRect mappedBounds = srcToDst.mapRect(imageBounds);
            if (mappedBounds.isEmpty()) {
                return SkImageFilters::Empty();
            }
            return sk_sp<SkImageFilter>(new SkImageImageFilter(std::move(image), imageBounds, mappedBounds, sampling));
        }
    }
}

void SkRegisterImageImageFilterFlattenable()
{
    SK_REGISTER_FLATTENABLE(SkImageImageFilter);
    // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
    SkFlattenable::Register("SkImageSourceImpl", SkImageImageFilter::CreateProc);
}

sk_sp<SkFlattenable> SkImageImageFilter::CreateProc(SkReadBuffer &buffer)
{
    SkSamplingOptions sampling;
    if (buffer.isVersionLT(SkPicturePriv::kImageFilterImageSampling_Version)) {
        sampling = SkSamplingPriv::FromFQ(buffer.checkFilterQuality(), kLinear_SkMediumAs);
    } else {
        sampling = buffer.readSampling();
    }

    SkRect src, dst;
    buffer.readRect(&src);
    buffer.readRect(&dst);

    sk_sp<SkImage> image(buffer.readImage());
    if (!image) {
        return nullptr;
    }

    return SkImageFilters::Image(std::move(image), src, dst, sampling);
}

void SkImageImageFilter::flatten(SkWriteBuffer &buffer) const
{
    buffer.writeSampling(fSampling);
    buffer.writeRect(fSrcRect);
    buffer.writeRect(SkRect(fDstRect));
    buffer.writeImage(fImage.get());
}

// /////////////////////////////////////////////////////////////////////////////////////////////////

skif::FilterResult SkImageImageFilter::onFilterImage(const skif::Context &ctx) const
{
    return skif::FilterResult::MakeFromImage(ctx, fImage, fSrcRect, fDstRect, fSampling);
}

skif::LayerSpace<SkIRect> SkImageImageFilter::onGetInputLayerBounds(const skif::Mapping &,
    const skif::LayerSpace<SkIRect> &, std::optional<skif::LayerSpace<SkIRect>>) const
{
    // This is a leaf filter, it requires no input and no further recursion
    return skif::LayerSpace<SkIRect>::Empty();
}

std::optional<skif::LayerSpace<SkIRect>> SkImageImageFilter::onGetOutputLayerBounds(const skif::Mapping &mapping,
    std::optional<skif::LayerSpace<SkIRect>>) const
{
    // The output is the transformed bounds of the image.
    return mapping.paramToLayer(fDstRect).roundOut();
}
